//! Image color filter implementations using Skia color matrix
//!
//! This module provides the core image filter functions that implement the
//! standard color adjustments as defined in the image filters proposal.
//! All filters use Skia's color matrix for efficient GPU-accelerated processing.
//!
//! ## Normalized vs Physical Values
//!
//! The module provides two sets of functions:
//! - **Normalized functions** (default): Accept values in the range [-1.0, 1.0] where:
//!   - `-1.0` = maximum negative adjustment
//!   - `0.0` = no change (neutral)
//!   - `1.0` = maximum positive adjustment
//! - **Physical functions**: Accept values in their original physical ranges for
//!   backward compatibility and direct control
//!
//! ## Filter Specifications
//!
//! ### Exposure
//! Controls the overall brightness of the image using the formula `RGB' = RGB * k`.
//! - **Normalized range**: [-1.0, 1.0] → Physical range: [0.25, 4.0]
//! - **Physical values**:
//!   - `0.25` = very dark (like -2 EV)
//!   - `0.5` = dark (like -1 EV)
//!   - `1.0` = original (no change)
//!   - `2.0` = bright (like +1 EV)
//!   - `4.0` = very bright (like +2 EV)
//!
//! ### Contrast
//! Controls the difference between light and dark areas using the formula `c' = (c - p) * k + p` where `p = 0.5` (pivot point).
//! - **Normalized range**: [-1.0, 1.0] → Physical range: [0.25, 4.0]
//! - **Physical values**:
//!   - `0.25` = very low contrast
//!   - `0.5` = low contrast
//!   - `1.0` = original contrast
//!   - `2.0` = high contrast
//!   - `4.0` = very high contrast
//!
//! ### Saturation
//! Controls the intensity of colors using the formula `color' = lerp(luma, color, k)` where `luma` is grayscale.
//! - **Normalized range**: [-1.0, 1.0] → Physical range: [0.0, 2.0]
//! - **Physical values**:
//!   - `0.0` = grayscale (no color)
//!   - `0.5` = desaturated
//!   - `1.0` = original saturation
//!   - `1.5` = oversaturated
//!   - `2.0` = highly oversaturated
//!
//! ### Temperature
//! Controls the warm/cool color balance using the formula `R' = R * rK`, `B' = B * bK` where `rK` and `bK` are temperature factors.
//! - **Normalized range**: [-1.0, 1.0] → Physical range: [-0.4, 0.4]
//! - **Physical values**:
//!   - `-0.4` = very cool (blue tint)
//!   - `-0.2` = cool (slight blue tint)
//!   - `0.0` = neutral (no change)
//!   - `0.2` = warm (slight orange tint)
//!   - `0.4` = very warm (strong orange tint)
//!
//! ### Tint
//! Controls the green/magenta color balance using the formula `G' = G * gK` where `gK` is the green multiplier.
//! - **Normalized range**: [-1.0, 1.0] → Physical range: [0.6, 1.4]
//! - **Physical values**:
//!   - `0.6` = strong magenta tint
//!   - `0.8` = slight magenta tint
//!   - `1.0` = neutral (no change)
//!   - `1.2` = slight green tint
//!   - `1.4` = strong green tint

use crate::cg::types::ImageFilters;
use skia_safe::{self as sk, color_filters, runtime_effect::RuntimeEffect, ColorMatrix, Data};

/// Conversion functions between normalized and physical values

/// Convert normalized exposure value (-1.0 to 1.0) to physical value (0.25 to 4.0)
fn normalized_to_physical_exposure(normalized: f32) -> f32 {
    // Map [-1.0, 1.0] to [0.25, 4.0] using exponential scaling
    // This preserves the EV relationship: k = 2^E
    if normalized <= 0.0 {
        // Map [-1.0, 0.0] to [0.25, 1.0]
        0.25 + (1.0 - 0.25) * (normalized + 1.0)
    } else {
        // Map [0.0, 1.0] to [1.0, 4.0]
        1.0 + (4.0 - 1.0) * normalized
    }
}

/// Convert normalized contrast value (-1.0 to 1.0) to physical value (0.25 to 4.0)
fn normalized_to_physical_contrast(normalized: f32) -> f32 {
    // Map [-1.0, 1.0] to [0.25, 4.0] using exponential scaling
    if normalized <= 0.0 {
        // Map [-1.0, 0.0] to [0.25, 1.0]
        0.25 + (1.0 - 0.25) * (normalized + 1.0)
    } else {
        // Map [0.0, 1.0] to [1.0, 4.0]
        1.0 + (4.0 - 1.0) * normalized
    }
}

/// Convert normalized saturation value (-1.0 to 1.0) to physical value (0.0 to 2.0)
fn normalized_to_physical_saturation(normalized: f32) -> f32 {
    // Map [-1.0, 1.0] to [0.0, 2.0]
    1.0 + normalized
}

/// Convert normalized temperature value (-1.0 to 1.0) to physical value (-0.4 to 0.4)
fn normalized_to_physical_temperature(normalized: f32) -> f32 {
    // Map [-1.0, 1.0] to [-0.4, 0.4]
    0.4 * normalized
}

/// Convert normalized tint value (-1.0 to 1.0) to physical value (0.6 to 1.4)
fn normalized_to_physical_tint(normalized: f32) -> f32 {
    // Map [-1.0, 1.0] to [0.6, 1.4]
    1.0 + 0.4 * normalized
}

/// Creates a color filter for exposure adjustment
///
/// Exposure adjusts the brightness of an image by multiplying RGB values by a factor.
///
/// # Math
/// RGB' = RGB * k (where k = 2^E, E is exposure value)
///
/// # Arguments
/// * `exposure_factor` - The exposure factor (k = 2^E)
///   - 1.0 = neutral (no change)
///   - > 1.0 = brighter (positive exposure)
///   - < 1.0 = darker (negative exposure)
///
/// # Returns
/// A Skia ColorFilter that can be applied to images or paints
pub fn create_exposure_filter(exposure_factor: f32) -> sk::ColorFilter {
    // Exposure is a simple multiplication of RGB channels
    // We clamp the result to [0, 1] range
    #[rustfmt::skip]
    let matrix = ColorMatrix::new(
        exposure_factor, 0.0, 0.0, 0.0, 0.0, // R' = R * k
        0.0, exposure_factor, 0.0, 0.0, 0.0, // G' = G * k
        0.0, 0.0, exposure_factor, 0.0, 0.0, // B' = B * k
        0.0, 0.0, 0.0, 1.0, 0.0,             // A' = A (unchanged)
    );

    color_filters::matrix(&matrix, None)
}

/// Creates a color filter for contrast adjustment
///
/// Contrast adjusts the difference between light and dark areas by scaling around a pivot point.
///
/// # Math
/// c' = (c - p) * k + p
/// Where:
/// - c = input color value [0, 1]
/// - p = pivot point (typically 0.5 for sRGB)
/// - k = contrast factor
///
/// # Arguments
/// * `contrast_factor` - The contrast factor (k)
///   - 1.0 = neutral (no change)
///   - > 1.0 = higher contrast
///   - < 1.0 = lower contrast
///   - 0.0 = flat gray
///
/// # Returns
/// A Skia ColorFilter that can be applied to images or paints
pub fn create_contrast_filter(contrast_factor: f32) -> sk::ColorFilter {
    let pivot = 0.5; // sRGB pivot point

    // Contrast formula: c' = (c - p) * k + p
    // This can be rewritten as: c' = c * k + p * (1 - k)
    // In matrix form: [k, 0, 0, 0, p*(1-k)]
    #[rustfmt::skip]
    let matrix = ColorMatrix::new(
        contrast_factor, 0.0, 0.0, 0.0, pivot * (1.0 - contrast_factor), // R' = R * k + p * (1 - k)
        0.0, contrast_factor, 0.0, 0.0, pivot * (1.0 - contrast_factor), // G' = G * k + p * (1 - k)
        0.0, 0.0, contrast_factor, 0.0, pivot * (1.0 - contrast_factor), // B' = B * k + p * (1 - k)
        0.0, 0.0, 0.0, 1.0, 0.0,                                         // A' = A (unchanged)
    );

    color_filters::matrix(&matrix, None)
}

/// Creates a color filter for saturation adjustment
///
/// Saturation adjusts the intensity of colors by interpolating between the original color
/// and its grayscale equivalent.
///
/// # Math
/// color' = lerp(luma, color, k)
/// Where:
/// - luma = dot(RGB, [0.2126, 0.7152, 0.0722]) - sRGB luminance coefficients
/// - k = saturation factor
///
/// # Arguments
/// * `saturation_factor` - The saturation factor (k)
///   - 1.0 = neutral (no change)
///   - > 1.0 = more saturated
///   - < 1.0 = less saturated
///   - 0.0 = grayscale
///
/// # Returns
/// A Skia ColorFilter that can be applied to images or paints
pub fn create_saturation_filter(saturation_factor: f32) -> sk::ColorFilter {
    // sRGB luminance coefficients
    let luma_r = 0.2126;
    let luma_g = 0.7152;
    let luma_b = 0.0722;

    // Saturation formula: color' = lerp(luma, color, k)
    // This expands to: color' = luma * (1 - k) + color * k
    // In matrix form for each channel:
    // R' = R * (luma_r * (1-k) + k) + G * (luma_g * (1-k)) + B * (luma_b * (1-k))
    // G' = R * (luma_r * (1-k)) + G * (luma_g * (1-k) + k) + B * (luma_b * (1-k))
    // B' = R * (luma_r * (1-k)) + G * (luma_g * (1-k)) + B * (luma_b * (1-k) + k)

    let one_minus_k = 1.0 - saturation_factor;

    #[rustfmt::skip]
    let matrix = ColorMatrix::new(
        luma_r * one_minus_k + saturation_factor, luma_g * one_minus_k, luma_b * one_minus_k, 0.0, 0.0, // R'
        luma_r * one_minus_k, luma_g * one_minus_k + saturation_factor, luma_b * one_minus_k, 0.0, 0.0, // G'
        luma_r * one_minus_k, luma_g * one_minus_k, luma_b * one_minus_k + saturation_factor, 0.0, 0.0, // B'
        0.0, 0.0, 0.0, 1.0, 0.0,                                                                         // A' = A (unchanged)
    );

    color_filters::matrix(&matrix, None)
}

/// Creates a color filter for temperature adjustment
///
/// Temperature adjusts the white balance by scaling R and B channels relative to each other.
///
/// # Math
/// R' = R * rK, B' = B * bK
/// Where:
/// - rK = 1 + t (red channel multiplier)
/// - bK = 1 - t (blue channel multiplier)
/// - t = temperature adjustment
///
/// # Arguments
/// * `temperature` - The temperature adjustment (t)
///   - 0.0 = neutral (no change)
///   - > 0.0 = warmer (more red, less blue)
///   - < 0.0 = cooler (less red, more blue)
///
/// # Returns
/// A Skia ColorFilter that can be applied to images or paints
pub fn create_temperature_filter(temperature: f32) -> sk::ColorFilter {
    // Temperature formula: R' = R * (1 + t), B' = B * (1 - t)
    // G channel remains unchanged to preserve luminance
    let red_multiplier = 1.0 + temperature;
    let blue_multiplier = 1.0 - temperature;

    #[rustfmt::skip]
    let matrix = ColorMatrix::new(
        red_multiplier, 0.0, 0.0, 0.0, 0.0, // R' = R * (1 + t)
        0.0, 1.0, 0.0, 0.0, 0.0,            // G' = G (unchanged)
        0.0, 0.0, blue_multiplier, 0.0, 0.0, // B' = B * (1 - t)
        0.0, 0.0, 0.0, 1.0, 0.0,            // A' = A (unchanged)
    );

    color_filters::matrix(&matrix, None)
}

/// Creates a color filter for tint adjustment
///
/// Tint adjusts the green-magenta balance by scaling the G channel relative to R and B.
///
/// # Math
/// G' = G * gK
/// Where:
/// - gK = green channel multiplier
///
/// # Arguments
/// * `green_multiplier` - The green channel multiplier (gK)
///   - 1.0 = neutral (no change)
///   - > 1.0 = more green (less magenta)
///   - < 1.0 = less green (more magenta)
///
/// # Returns
/// A Skia ColorFilter that can be applied to images or paints
pub fn create_tint_filter(green_multiplier: f32) -> sk::ColorFilter {
    // Tint formula: G' = G * gK
    // R and B channels remain unchanged
    #[rustfmt::skip]
    let matrix = ColorMatrix::new(
        1.0, 0.0, 0.0, 0.0, 0.0,            // R' = R (unchanged)
        0.0, green_multiplier, 0.0, 0.0, 0.0, // G' = G * gK
        0.0, 0.0, 1.0, 0.0, 0.0,            // B' = B (unchanged)
        0.0, 0.0, 0.0, 1.0, 0.0,            // A' = A (unchanged)
    );

    color_filters::matrix(&matrix, None)
}

/// Parameters for the shadows/highlights filter
#[derive(Debug, Clone)]
pub struct ShadowsHighlightsParams {
    /// Shadow adjustment [-1.0, +1.0] (>0 lift shadows, <0 crush)
    pub shadows: f32,
    /// Highlight adjustment [-1.0, +1.0] (>0 recover/compress brights, <0 boost)
    pub highlights: f32,
    /// Shadow mask window low threshold (e.g., 0.25)
    pub sh_lo: f32,
    /// Shadow mask window high threshold (e.g., 0.80)
    pub sh_hi: f32,
    /// Highlight mask window low threshold (e.g., 0.20)
    pub hi_lo: f32,
    /// Highlight mask window high threshold (e.g., 0.90)
    pub hi_hi: f32,
    /// Chroma preservation factor [0.0, 1.0] (0 = grayscale-only, 1 = hue-preserving)
    pub chroma_preserve: f32,
    /// Luma coefficients for linear RGB (R, G, B)
    pub luma_coeff: (f32, f32, f32),
}

impl Default for ShadowsHighlightsParams {
    fn default() -> Self {
        Self {
            shadows: 0.0,
            highlights: 0.0,
            sh_lo: 0.25,
            sh_hi: 0.80,
            hi_lo: 0.20,
            hi_hi: 0.90,
            chroma_preserve: 0.85,
            luma_coeff: (0.2126, 0.7152, 0.0722),
        }
    }
}

/// Compile the shadows/highlights SkSL shader
fn runtime_effect_shadows_highlights() -> RuntimeEffect {
    let sksl = include_str!("../shaders/shadows_highlights.sksl");
    RuntimeEffect::make_for_color_filter(sksl, None)
        .expect("Failed to compile shadows/highlights SkSL")
}

/// Creates a color filter for shadows/highlights adjustment using SkSL
///
/// This filter provides advanced shadow and highlight recovery/compression
/// with chroma preservation and smooth masking. It operates in linear light
/// space for more accurate color processing.
///
/// # Math
/// The filter uses smooth masking to selectively adjust shadows and highlights:
/// - Shadow mask: smooth transition from shadows to midtones
/// - Highlight mask: smooth transition from midtones to highlights
/// - Luma adjustment: Y' = Y + shadow_adjust + highlight_adjust
/// - Chroma preservation: RGB' = lerp(grayscale, RGB * factor, chroma_preserve)
///
/// # Arguments
/// * `params` - The ShadowsHighlightsParams containing all filter parameters
///
/// # Returns
/// A Skia ColorFilter that can be applied to images or paints
pub fn create_shadows_highlights_filter(params: ShadowsHighlightsParams) -> sk::ColorFilter {
    let effect = runtime_effect_shadows_highlights();

    // Create uniform data buffer
    // The uniforms are packed in the order they appear in the SkSL shader
    let mut uniform_data = Vec::new();

    // Pack uniforms as f32 values in the order they appear in the shader
    uniform_data.extend_from_slice(&params.shadows.to_le_bytes());
    uniform_data.extend_from_slice(&params.highlights.to_le_bytes());
    uniform_data.extend_from_slice(&params.sh_lo.to_le_bytes());
    uniform_data.extend_from_slice(&params.sh_hi.to_le_bytes());
    uniform_data.extend_from_slice(&params.hi_lo.to_le_bytes());
    uniform_data.extend_from_slice(&params.hi_hi.to_le_bytes());
    uniform_data.extend_from_slice(&params.chroma_preserve.to_le_bytes());
    uniform_data.extend_from_slice(&params.luma_coeff.0.to_le_bytes());
    uniform_data.extend_from_slice(&params.luma_coeff.1.to_le_bytes());
    uniform_data.extend_from_slice(&params.luma_coeff.2.to_le_bytes());

    let data = Data::new_copy(&uniform_data);
    effect
        .make_color_filter(data, None)
        .expect("Failed to create shadows/highlights color filter")
}

/// Combines multiple color filters into a single filter for efficient processing
///
/// This function allows combining multiple linear color adjustments into a single
/// color matrix operation, which is more efficient than applying filters sequentially.
///
/// # Arguments
/// * `filters` - A slice of Skia ColorFilters to combine
///
/// # Returns
/// A combined Skia ColorFilter, or None if the slice is empty
pub fn combine_color_filters(filters: &[sk::ColorFilter]) -> Option<sk::ColorFilter> {
    if filters.is_empty() {
        return None;
    }

    if filters.len() == 1 {
        return Some(filters[0].clone());
    }

    // For multiple filters, we need to combine them
    // This is a simplified implementation - in practice, you might want to
    // combine the color matrices directly for better performance
    let mut combined = filters[0].clone();
    for filter in &filters[1..] {
        if let Some(new_combined) = color_filters::compose(filter, &combined) {
            combined = new_combined;
        } else {
            // If composition fails, return None
            return None;
        }
    }

    Some(combined)
}

/// Creates a combined color filter from ImageFilters struct using normalized values
///
/// This function takes an ImageFilters struct with normalized values (-1.0 to 1.0)
/// and creates a single combined color filter that applies all the specified filters
/// in the correct order. The filters are applied in the order:
/// Exposure -> Contrast -> Saturation -> Temperature -> Tint
///
/// # Arguments
/// * `filters` - The ImageFilters struct containing normalized filter parameters
///
/// # Returns
/// A combined Skia ColorFilter, or None if no filters are specified
pub fn create_image_filters_color_filter(filters: &ImageFilters) -> Option<sk::ColorFilter> {
    let mut color_filters = Vec::new();

    // Apply filters in the correct order for optimal results
    // 1. Exposure (brightness adjustment)
    if filters.exposure != 0.0 {
        let physical_exposure = normalized_to_physical_exposure(filters.exposure);
        color_filters.push(create_exposure_filter(physical_exposure));
    }

    // 2. Contrast (dynamic range adjustment)
    if filters.contrast != 0.0 {
        let physical_contrast = normalized_to_physical_contrast(filters.contrast);
        color_filters.push(create_contrast_filter(physical_contrast));
    }

    // 3. Saturation (color intensity adjustment)
    if filters.saturation != 0.0 {
        let physical_saturation = normalized_to_physical_saturation(filters.saturation);
        color_filters.push(create_saturation_filter(physical_saturation));
    }

    // 4. Temperature (warm/cool color adjustment)
    if filters.temperature != 0.0 {
        let physical_temperature = normalized_to_physical_temperature(filters.temperature);
        color_filters.push(create_temperature_filter(physical_temperature));
    }

    // 5. Tint (green/magenta adjustment)
    if filters.tint != 0.0 {
        let physical_tint = normalized_to_physical_tint(filters.tint);
        color_filters.push(create_tint_filter(physical_tint));
    }

    // 6. Highlights and Shadows (using the existing shadows/highlights filter)
    if filters.highlights != 0.0 || filters.shadows != 0.0 {
        let params = ShadowsHighlightsParams {
            shadows: filters.shadows,
            highlights: filters.highlights,
            sh_lo: 0.25,
            sh_hi: 0.80,
            hi_lo: 0.20,
            hi_hi: 0.90,
            chroma_preserve: 0.85,
            luma_coeff: (0.2126, 0.7152, 0.0722),
        };
        color_filters.push(create_shadows_highlights_filter(params));
    }

    // Combine all filters into a single color filter
    combine_color_filters(&color_filters)
}

/// Creates a combined color filter from ImageFilters struct using physical values
///
/// This function takes an ImageFilters struct with physical values and creates a single
/// combined color filter that applies all the specified filters in the correct order.
/// This is provided for backward compatibility and direct control over filter parameters.
///
/// # Arguments
/// * `filters` - The ImageFilters struct containing physical filter parameters
///
/// # Returns
/// A combined Skia ColorFilter, or None if no filters are specified
pub fn create_image_filters_color_filter_physical(
    filters: &ImageFilters,
) -> Option<sk::ColorFilter> {
    let mut color_filters = Vec::new();

    // Apply filters in the correct order for optimal results
    // 1. Exposure (brightness adjustment)
    if filters.exposure != 1.0 {
        color_filters.push(create_exposure_filter(filters.exposure));
    }

    // 2. Contrast (dynamic range adjustment)
    if filters.contrast != 1.0 {
        color_filters.push(create_contrast_filter(filters.contrast));
    }

    // 3. Saturation (color intensity adjustment)
    if filters.saturation != 1.0 {
        color_filters.push(create_saturation_filter(filters.saturation));
    }

    // 4. Temperature (warm/cool color adjustment)
    if filters.temperature != 0.0 {
        color_filters.push(create_temperature_filter(filters.temperature));
    }

    // 5. Tint (green/magenta adjustment)
    if filters.tint != 1.0 {
        color_filters.push(create_tint_filter(filters.tint));
    }

    // 6. Highlights and Shadows (using the existing shadows/highlights filter)
    if filters.highlights != 0.0 || filters.shadows != 0.0 {
        let params = ShadowsHighlightsParams {
            shadows: filters.shadows,
            highlights: filters.highlights,
            sh_lo: 0.25,
            sh_hi: 0.80,
            hi_lo: 0.20,
            hi_hi: 0.90,
            chroma_preserve: 0.85,
            luma_coeff: (0.2126, 0.7152, 0.0722),
        };
        color_filters.push(create_shadows_highlights_filter(params));
    }

    // Combine all filters into a single color filter
    combine_color_filters(&color_filters)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_exposure_filter_neutral() {
        let _filter = create_exposure_filter(1.0);
        // Test that neutral exposure doesn't change colors
        // This would require more complex testing with actual color values
        // The filter creation should not panic
    }

    #[test]
    fn test_contrast_filter_neutral() {
        let _filter = create_contrast_filter(1.0);
        // The filter creation should not panic
    }

    #[test]
    fn test_saturation_filter_neutral() {
        let _filter = create_saturation_filter(1.0);
        // The filter creation should not panic
    }

    #[test]
    fn test_temperature_filter_neutral() {
        let _filter = create_temperature_filter(0.0);
        // The filter creation should not panic
    }

    #[test]
    fn test_tint_filter_neutral() {
        let _filter = create_tint_filter(1.0);
        // The filter creation should not panic
    }

    #[test]
    fn test_combine_empty_filters() {
        let result = combine_color_filters(&[]);
        assert!(result.is_none());
    }

    #[test]
    fn test_combine_single_filter() {
        let filter = create_exposure_filter(1.5);
        let result = combine_color_filters(&[filter]);
        assert!(result.is_some());
    }

    #[test]
    fn test_create_image_filters_color_filter_empty() {
        let filters = ImageFilters::default();
        let result = create_image_filters_color_filter(&filters);
        assert!(result.is_none());
    }

    #[test]
    fn test_create_image_filters_color_filter_single() {
        let mut filters = ImageFilters::default();
        filters.exposure = 0.5; // Normalized value
        let result = create_image_filters_color_filter(&filters);
        assert!(result.is_some());
    }

    #[test]
    fn test_create_image_filters_color_filter_multiple() {
        let mut filters = ImageFilters::default();
        filters.exposure = 0.2; // Normalized value
        filters.contrast = 0.1; // Normalized value
        filters.saturation = -0.2; // Normalized value
        let result = create_image_filters_color_filter(&filters);
        assert!(result.is_some());
    }

    #[test]
    fn test_create_image_filters_color_filter_physical_single() {
        let mut filters = ImageFilters::default();
        filters.exposure = 1.5; // Physical value
        let result = create_image_filters_color_filter_physical(&filters);
        assert!(result.is_some());
    }

    #[test]
    fn test_create_image_filters_color_filter_physical_multiple() {
        let mut filters = ImageFilters::default();
        filters.exposure = 1.2; // Physical value
        filters.contrast = 1.1; // Physical value
        filters.saturation = 0.8; // Physical value
        let result = create_image_filters_color_filter_physical(&filters);
        assert!(result.is_some());
    }

    #[test]
    fn test_shadows_highlights_filter_default() {
        let params = ShadowsHighlightsParams::default();
        let _filter = create_shadows_highlights_filter(params);
        // The filter creation should not panic
    }

    #[test]
    fn test_shadows_highlights_filter_custom() {
        let params = ShadowsHighlightsParams {
            shadows: 0.5,
            highlights: 0.3,
            sh_lo: 0.2,
            sh_hi: 0.7,
            hi_lo: 0.3,
            hi_hi: 0.8,
            chroma_preserve: 0.9,
            luma_coeff: (0.2126, 0.7152, 0.0722),
        };
        let _filter = create_shadows_highlights_filter(params);
        // The filter creation should not panic
    }
}
